﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

using SharedGenomics.Core;

namespace SharedGenomics.Domain.Builders
{
    public class FilterParameterSetBuilder : IBuilder<FilterParameterSetInfo>
    {
        private decimal? _individualMissingness;
        private decimal? _snpMissingness;
        private decimal? _maf;
        private decimal? _p;
        private string _phenotypeColumn;
        private IDictionary<string, string[]> _categoryFilters;

        public FilterParameterSetBuilder()
        {
            this._categoryFilters = new Dictionary<string, string[]>();
        }

        public FilterParameterSetBuilder WithIndividualMissingness(decimal? individualMissingess)
        {
            this._individualMissingness = individualMissingess;
            return this;
        }

        public FilterParameterSetBuilder WithSnpMissingness(decimal? snpMissingness)
        {
            this._snpMissingness = snpMissingness;
            return this;
        }

        public FilterParameterSetBuilder WithMAF(decimal? maf)
        {
            this._maf = maf;
            return this;
        }

        public FilterParameterSetBuilder WithHardyWeinberg(decimal? p, string phenotypeColumn)
        {
            this._p = p;
            this._phenotypeColumn = phenotypeColumn;
            return this;
        }

        public FilterParameterSetBuilder AddCategoricalFilter(string columnName, IEnumerable<string> permittedValues)
        {
            string[] filterValues = (permittedValues ?? Enumerable.Empty<string>()).Where(v => !v.IsEmptyOrWhitespace()).ToArray();
            if (columnName != null && filterValues.Length > 0)
            {
                this._categoryFilters[columnName] = filterValues;
            }

            return this;
        }

        public FilterParameterSetInfo Build()
        {
            var propertyErrors = this.Validate();
            ExceptionHelper.ThrowIf<BuilderStateException>(!propertyErrors.IsValid, "Builder invalid");

            return new FilterParameterSetInfo
            {
                IndividualMissingnessRate = this._individualMissingness,
                SnpGenotypingRate = this._snpMissingness,
                MAF = this._maf,
                Phenotype = this._phenotypeColumn,
                HardyWeinbergP = this._p,
                CategoryFilters = this._categoryFilters.Select(kvp => new CategoryFilterInfo { Category = kvp.Key, FilterValues = kvp.Value.ToArray() }).ToArray()
            };
        }

        public PropertyValidationErrorCollection Validate()
        {
            PropertyValidationErrorCollection errors = new PropertyValidationErrorCollection();

            //if individual missingness exists it should be in the range [0, 1]
            this.AddErrorIfOptionalParameterInvalid(this._individualMissingness, errors, "IndividualMissingnessRate", "individual missingness");
            this.AddErrorIfOptionalParameterInvalid(this._snpMissingness, errors, "SnpGenotypingRate", "snp missingness");
            this.AddErrorIfOptionalParameterInvalid(this._maf, errors, "MAF", "MAF");

            bool isHWEValid = this.AddErrorIfOptionalParameterInvalid(this._p, errors, "HardyWeinbergP", "HWE");

            if (!(this._p.HasValue ^ this._phenotypeColumn == null))
            {
                //only one of HWE and phenotype column are specified - add an error for the missing value
                string missingProperty = this._p.HasValue ? "Phenotype" : "HardyWeinbergP";
                string missingPropertyDisplay = this._p.HasValue ? "phenotype" : "HWE";

                errors.AddError(missingProperty, String.Format("{0} required for Hardy-Weinberg filter", missingPropertyDisplay));
            }

            return errors;
        }

        private bool IsOptionalParameterValid(decimal? value)
        {
            return !value.HasValue || value.Value.IsBetween(0m, 1m, true);
        }

        private bool AddErrorIfOptionalParameterInvalid(decimal? value, PropertyValidationErrorCollection errors, string propertyName, string displayName)
        {
            Debug.Assert(errors != null);
            Debug.Assert(propertyName != null);

            bool parameterValid = this.IsOptionalParameterValid(value);
            if (!parameterValid)
            {
                errors.AddError(propertyName, String.Format("{0} must be in the range [0, 1]", displayName.ToLower()));
            }

            return parameterValid;
        }
    }
}
